﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SocialKit.LightRest
{
    /// <summary>
    /// Provides helper methods for common tasks.
    /// </summary>
    public static class RestUtility
    {
        static string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";

        /// <summary>
        /// Represents the System.Text.Encoding.Default.
        /// </summary>
        public static readonly Encoding DefaultEncoding = Encoding.Default;

        /// <summary>
        /// Encodes a URL string with UTF8 encoding.
        /// </summary>
        /// <param name="source">The text to encode.</param>
        /// <returns></returns>
        public static string UrlEncode(string source)
        {
            return UrlEncode(source, Encoding.UTF8);
        }

        /// <summary>
        /// Encodes a URL string with the specified encoding.
        /// </summary>
        /// <param name="source">The text to encode.</param>
        /// <param name="encoding">The System.Text.Encoding object that specifies the encoding scheme.</param>
        /// <returns></returns>
        public static string UrlEncode(string source, Encoding encoding)
        {
            if (source == null)
                return null;

            if (string.IsNullOrEmpty(source))
                return string.Empty;

            if (encoding == null)
                encoding = DefaultEncoding;

            var buffer = new StringBuilder();

            foreach (var b in encoding.GetBytes(source))
            {
                if (b < 128 && unreservedChars.IndexOf((char)b) != -1)
                {
                    buffer.Append((char)b);
                }
                else
                {
                    buffer.AppendFormat("%{0:X2}", b);
                }
            }

            return buffer.ToString();
        }

        /// <summary>
        /// Converts a string that has been encoded for transmission in a URL into a decoded string. 
        /// </summary>
        /// <param name="source">The URL-encoded string to decode.</param>
        /// <returns></returns>
        public static string UrlDecode(string source)
        {
            return UrlDecode(source, Encoding.UTF8);
        }

        /// <summary>
        /// Converts a URL-encoded string into a decoded string, using the specified encoding object. 
        /// </summary>
        /// <param name="source">The URL-encoded string to decode.</param>
        /// <param name="encoding">The System.Text.Encoding object that specifies the encoding scheme.</param>
        /// <returns></returns>
        public static string UrlDecode(string source, Encoding encoding)
        {
            if (source == null)
                return null;

            if (string.IsNullOrEmpty(source))
                return string.Empty;

            if (encoding == null)
                encoding = DefaultEncoding;

            var bytes = new List<byte>();

            for (int i = 0; i < source.Length; i++)
            {
                var c = source[i];

                if (unreservedChars.IndexOf(c) != -1)
                {
                    bytes.Add((byte)c);
                }
                else if (c == '%' && i < source.Length - 2)
                {
                    var n1 = HexToInt(source[i + 1]);
                    var n2 = HexToInt(source[i + 2]);

                    if (n1 >= 0 && n2 >= 0)
                    {
                        var b = (byte)((n1 << 4) | n2);

                        i += 2;

                        bytes.Add(b);
                    }
                }
            }

            return encoding.GetString(bytes.ToArray(), 0, bytes.Count);
        }

        /// <summary>
        /// Converts an hex char to an integer.
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        static int HexToInt(char c)
        {
            if ((c >= '0') && (c <= '9'))
            {
                return (c - '0');
            }

            if ((c >= 'a') && (c <= 'f'))
            {
                return ((c - 'a') + 10);
            }

            if ((c >= 'A') && (c <= 'F'))
            {
                return ((c - 'A') + 10);
            }

            return -1;
        }

        /// <summary>
        /// Parse the query string in the URI into a KeyValuePair&lt;string, string&gt; collection.
        /// The keys and values will be URL decoded.
        /// </summary>
        /// <param name="query">The query string to parse.</param>
        /// <returns>A keys and values collection.</returns>
        public static IEnumerable<KeyValuePair<string, string>> ParseQueryString(string query)
        {
            if (query == null)
                throw new ArgumentNullException("query");

            if (query.StartsWith("?"))
                query = query.Substring(1);

            if (string.IsNullOrEmpty(query))
                return new KeyValuePair<string, string>[] { };

            var values = new List<KeyValuePair<string, string>>();

            int length = query.Length;
            int startIndex, equalIndex;

            for (int i = 0; i < length; i++)
            {
                startIndex = i;
                equalIndex = -1;

                while (i < length)
                {
                    char c = query[i];

                    if (c == '=')
                    {
                        if (equalIndex < 0)
                        {
                            equalIndex = i;
                        }
                    }
                    else if (c == '&')
                    {
                        break;
                    }

                    i++;
                }

                string key = null;
                string value = null;

                if (equalIndex >= 0)
                {
                    key = query.Substring(startIndex, equalIndex - startIndex);
                    value = query.Substring(equalIndex + 1, (i - equalIndex) - 1);
                }
                else
                {
                    //standalone value is key or value?
                    key = query.Substring(startIndex, i - startIndex);
                }

                if (value == null)
                {
                    value = string.Empty;
                }

                values.Add(new KeyValuePair<string, string>(UrlDecode(key), UrlDecode(value)));
            }

            return values;
        }

        /// <summary>
        /// Gets a string indicates the URI without query string.
        /// </summary>
        /// <param name="uri">The URI to normalize.</param>
        /// <returns>The host and path of the URI.</returns>
        public static string NormalizeUriWithoutQuery(Uri uri)
        {
            if (!uri.IsAbsoluteUri)
                throw new ArgumentOutOfRangeException("uri", "The relative URI is not supported.");

            var normalizedUri = string.Format("{0}://{1}", uri.Scheme, uri.Host);

            if (!((uri.Scheme == "http" && uri.Port == 80) || (uri.Scheme == "https" && uri.Port == 443)))
            {
                normalizedUri += ":" + uri.Port;
            }

            normalizedUri += uri.AbsolutePath;

            return normalizedUri;
        }

        /// <summary>
        /// Extract encoding from the contentType string, or returns the fallback value.
        /// </summary>
        /// <param name="contentType">The contentType string.</param>
        /// <param name="fallback">If no encoding found, returns this value.</param>
        /// <returns>A System.Text.Encoding value.</returns>
        public static Encoding ExtractEncoding(string contentType, Encoding fallback)
        {
            if (string.IsNullOrEmpty(contentType))
            {
                return fallback;
            }

            //find charset
            var values = contentType.Split(';');

            var charsetPair = values.FirstOrDefault(v => v.Trim().StartsWith("charset", StringComparison.InvariantCultureIgnoreCase));

            if (charsetPair == null)
                return fallback;

            var charset = charsetPair.Split('=')[1];

            return Encoding.GetEncoding(charset);
        }

        /// <summary>
        /// Extract encoding from the contentType string.
        /// </summary>
        /// <param name="contentType">The contentType string.</param>
        /// <returns>A System.Text.Encoding value.</returns>
        public static Encoding ExtractEncoding(string contentType)
        {
            return ExtractEncoding(contentType, Encoding.Default);
        }
    }
}